rulesync 7.20.0 → 7.21.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 +1 -0
- package/dist/{chunk-5OPNV62F.js → chunk-UYWCICY6.js} +877 -664
- package/dist/cli/index.cjs +1365 -1071
- package/dist/cli/index.js +330 -253
- package/dist/index.cjs +750 -544
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +7 -7
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -63,18 +63,20 @@ import {
|
|
|
63
63
|
importFromTool,
|
|
64
64
|
isSymlink,
|
|
65
65
|
listDirectoryFiles,
|
|
66
|
-
logger,
|
|
67
66
|
readFileContent,
|
|
68
67
|
removeDirectory,
|
|
69
68
|
removeFile,
|
|
70
69
|
removeTempDirectory,
|
|
71
70
|
stringifyFrontmatter,
|
|
72
71
|
writeFileContent
|
|
73
|
-
} from "../chunk-
|
|
72
|
+
} from "../chunk-UYWCICY6.js";
|
|
74
73
|
|
|
75
74
|
// src/cli/index.ts
|
|
76
75
|
import { Command } from "commander";
|
|
77
76
|
|
|
77
|
+
// src/utils/parse-comma-separated-list.ts
|
|
78
|
+
var parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
79
|
+
|
|
78
80
|
// src/lib/fetch.ts
|
|
79
81
|
import { join } from "path";
|
|
80
82
|
import { Semaphore } from "es-toolkit/promise";
|
|
@@ -140,13 +142,14 @@ var GitHubClientError = class extends Error {
|
|
|
140
142
|
this.name = "GitHubClientError";
|
|
141
143
|
}
|
|
142
144
|
};
|
|
143
|
-
function logGitHubAuthHints(
|
|
144
|
-
|
|
145
|
+
function logGitHubAuthHints(params) {
|
|
146
|
+
const { error, logger: logger5 } = params;
|
|
147
|
+
logger5.error(`GitHub API Error: ${error.message}`);
|
|
145
148
|
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
146
|
-
|
|
149
|
+
logger5.info(
|
|
147
150
|
"Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for private repositories or better rate limits."
|
|
148
151
|
);
|
|
149
|
-
|
|
152
|
+
logger5.info(
|
|
150
153
|
"Tip: If you use GitHub CLI, you can use `GITHUB_TOKEN=$(gh auth token) rulesync fetch ...`"
|
|
151
154
|
);
|
|
152
155
|
}
|
|
@@ -563,38 +566,38 @@ async function processFeatureConversion(params) {
|
|
|
563
566
|
return { paths };
|
|
564
567
|
}
|
|
565
568
|
async function convertFetchedFilesToRulesync(params) {
|
|
566
|
-
const { tempDir, outputDir, target, features } = params;
|
|
569
|
+
const { tempDir, outputDir, target, features, logger: logger5 } = params;
|
|
567
570
|
const convertedPaths = [];
|
|
568
571
|
const featureConfigs = [
|
|
569
572
|
{
|
|
570
573
|
feature: "rules",
|
|
571
574
|
getTargets: () => RulesProcessor.getToolTargets({ global: false }),
|
|
572
|
-
createProcessor: () => new RulesProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
575
|
+
createProcessor: () => new RulesProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
573
576
|
},
|
|
574
577
|
{
|
|
575
578
|
feature: "commands",
|
|
576
579
|
getTargets: () => CommandsProcessor.getToolTargets({ global: false, includeSimulated: false }),
|
|
577
|
-
createProcessor: () => new CommandsProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
580
|
+
createProcessor: () => new CommandsProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
578
581
|
},
|
|
579
582
|
{
|
|
580
583
|
feature: "subagents",
|
|
581
584
|
getTargets: () => SubagentsProcessor.getToolTargets({ global: false, includeSimulated: false }),
|
|
582
|
-
createProcessor: () => new SubagentsProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
585
|
+
createProcessor: () => new SubagentsProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
583
586
|
},
|
|
584
587
|
{
|
|
585
588
|
feature: "ignore",
|
|
586
589
|
getTargets: () => IgnoreProcessor.getToolTargets(),
|
|
587
|
-
createProcessor: () => new IgnoreProcessor({ baseDir: tempDir, toolTarget: target })
|
|
590
|
+
createProcessor: () => new IgnoreProcessor({ baseDir: tempDir, toolTarget: target, logger: logger5 })
|
|
588
591
|
},
|
|
589
592
|
{
|
|
590
593
|
feature: "mcp",
|
|
591
594
|
getTargets: () => McpProcessor.getToolTargets({ global: false }),
|
|
592
|
-
createProcessor: () => new McpProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
595
|
+
createProcessor: () => new McpProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
593
596
|
},
|
|
594
597
|
{
|
|
595
598
|
feature: "hooks",
|
|
596
599
|
getTargets: () => HooksProcessor.getToolTargets({ global: false }),
|
|
597
|
-
createProcessor: () => new HooksProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
600
|
+
createProcessor: () => new HooksProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
598
601
|
}
|
|
599
602
|
];
|
|
600
603
|
for (const config of featureConfigs) {
|
|
@@ -610,7 +613,7 @@ async function convertFetchedFilesToRulesync(params) {
|
|
|
610
613
|
convertedPaths.push(...result.paths);
|
|
611
614
|
}
|
|
612
615
|
if (features.includes("skills")) {
|
|
613
|
-
|
|
616
|
+
logger5.debug(
|
|
614
617
|
"Skills conversion is not yet supported in fetch command. Use import command instead."
|
|
615
618
|
);
|
|
616
619
|
}
|
|
@@ -639,7 +642,7 @@ function isNotFoundError(error) {
|
|
|
639
642
|
return false;
|
|
640
643
|
}
|
|
641
644
|
async function fetchFiles(params) {
|
|
642
|
-
const { source, options = {}, baseDir = process.cwd() } = params;
|
|
645
|
+
const { source, options = {}, baseDir = process.cwd(), logger: logger5 } = params;
|
|
643
646
|
const parsed = parseSource(source);
|
|
644
647
|
if (parsed.provider === "gitlab") {
|
|
645
648
|
throw new Error(
|
|
@@ -658,7 +661,7 @@ async function fetchFiles(params) {
|
|
|
658
661
|
});
|
|
659
662
|
const token = GitHubClient.resolveToken(options.token);
|
|
660
663
|
const client = new GitHubClient({ token });
|
|
661
|
-
|
|
664
|
+
logger5.debug(`Validating repository: ${parsed.owner}/${parsed.repo}`);
|
|
662
665
|
const isValid = await client.validateRepository(parsed.owner, parsed.repo);
|
|
663
666
|
if (!isValid) {
|
|
664
667
|
throw new GitHubClientError(
|
|
@@ -667,7 +670,7 @@ async function fetchFiles(params) {
|
|
|
667
670
|
);
|
|
668
671
|
}
|
|
669
672
|
const ref = resolvedRef ?? await client.getDefaultBranch(parsed.owner, parsed.repo);
|
|
670
|
-
|
|
673
|
+
logger5.debug(`Using ref: ${ref}`);
|
|
671
674
|
if (isToolTarget(target)) {
|
|
672
675
|
return fetchAndConvertToolFiles({
|
|
673
676
|
client,
|
|
@@ -678,7 +681,8 @@ async function fetchFiles(params) {
|
|
|
678
681
|
target,
|
|
679
682
|
outputDir,
|
|
680
683
|
baseDir,
|
|
681
|
-
conflictStrategy
|
|
684
|
+
conflictStrategy,
|
|
685
|
+
logger: logger5
|
|
682
686
|
});
|
|
683
687
|
}
|
|
684
688
|
const semaphore = new Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
@@ -689,10 +693,11 @@ async function fetchFiles(params) {
|
|
|
689
693
|
basePath: resolvedPath,
|
|
690
694
|
ref,
|
|
691
695
|
enabledFeatures,
|
|
692
|
-
semaphore
|
|
696
|
+
semaphore,
|
|
697
|
+
logger: logger5
|
|
693
698
|
});
|
|
694
699
|
if (filesToFetch.length === 0) {
|
|
695
|
-
|
|
700
|
+
logger5.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
|
|
696
701
|
return {
|
|
697
702
|
source: `${parsed.owner}/${parsed.repo}`,
|
|
698
703
|
ref,
|
|
@@ -715,7 +720,7 @@ async function fetchFiles(params) {
|
|
|
715
720
|
const localPath = join(outputBasePath, relativePath);
|
|
716
721
|
const exists = await fileExists(localPath);
|
|
717
722
|
if (exists && conflictStrategy === "skip") {
|
|
718
|
-
|
|
723
|
+
logger5.debug(`Skipping existing file: ${relativePath}`);
|
|
719
724
|
return { relativePath, status: "skipped" };
|
|
720
725
|
}
|
|
721
726
|
const content = await withSemaphore(
|
|
@@ -724,7 +729,7 @@ async function fetchFiles(params) {
|
|
|
724
729
|
);
|
|
725
730
|
await writeFileContent(localPath, content);
|
|
726
731
|
const status = exists ? "overwritten" : "created";
|
|
727
|
-
|
|
732
|
+
logger5.debug(`Wrote: ${relativePath} (${status})`);
|
|
728
733
|
return { relativePath, status };
|
|
729
734
|
})
|
|
730
735
|
);
|
|
@@ -739,7 +744,7 @@ async function fetchFiles(params) {
|
|
|
739
744
|
return summary;
|
|
740
745
|
}
|
|
741
746
|
async function collectFeatureFiles(params) {
|
|
742
|
-
const { client, owner, repo, basePath, ref, enabledFeatures, semaphore } = params;
|
|
747
|
+
const { client, owner, repo, basePath, ref, enabledFeatures, semaphore, logger: logger5 } = params;
|
|
743
748
|
const dirCache = /* @__PURE__ */ new Map();
|
|
744
749
|
async function getCachedDirectory(path2) {
|
|
745
750
|
let promise = dirCache.get(path2);
|
|
@@ -772,7 +777,7 @@ async function collectFeatureFiles(params) {
|
|
|
772
777
|
}
|
|
773
778
|
} catch (error) {
|
|
774
779
|
if (isNotFoundError(error)) {
|
|
775
|
-
|
|
780
|
+
logger5.debug(`File not found: ${fullPath}`);
|
|
776
781
|
} else {
|
|
777
782
|
throw error;
|
|
778
783
|
}
|
|
@@ -797,7 +802,7 @@ async function collectFeatureFiles(params) {
|
|
|
797
802
|
}
|
|
798
803
|
} catch (error) {
|
|
799
804
|
if (isNotFoundError(error)) {
|
|
800
|
-
|
|
805
|
+
logger5.debug(`Feature not found: ${fullPath}`);
|
|
801
806
|
return collected;
|
|
802
807
|
}
|
|
803
808
|
throw error;
|
|
@@ -817,10 +822,11 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
817
822
|
target,
|
|
818
823
|
outputDir,
|
|
819
824
|
baseDir,
|
|
820
|
-
conflictStrategy: _conflictStrategy
|
|
825
|
+
conflictStrategy: _conflictStrategy,
|
|
826
|
+
logger: logger5
|
|
821
827
|
} = params;
|
|
822
828
|
const tempDir = await createTempDirectory();
|
|
823
|
-
|
|
829
|
+
logger5.debug(`Created temp directory: ${tempDir}`);
|
|
824
830
|
const semaphore = new Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
825
831
|
try {
|
|
826
832
|
const filesToFetch = await collectFeatureFiles({
|
|
@@ -830,10 +836,11 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
830
836
|
basePath: resolvedPath,
|
|
831
837
|
ref,
|
|
832
838
|
enabledFeatures,
|
|
833
|
-
semaphore
|
|
839
|
+
semaphore,
|
|
840
|
+
logger: logger5
|
|
834
841
|
});
|
|
835
842
|
if (filesToFetch.length === 0) {
|
|
836
|
-
|
|
843
|
+
logger5.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
|
|
837
844
|
return {
|
|
838
845
|
source: `${parsed.owner}/${parsed.repo}`,
|
|
839
846
|
ref,
|
|
@@ -860,7 +867,7 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
860
867
|
() => client.getFileContent(parsed.owner, parsed.repo, remotePath, ref)
|
|
861
868
|
);
|
|
862
869
|
await writeFileContent(localPath, content);
|
|
863
|
-
|
|
870
|
+
logger5.debug(`Fetched to temp: ${toolRelativePath}`);
|
|
864
871
|
})
|
|
865
872
|
);
|
|
866
873
|
const outputBasePath = join(baseDir, outputDir);
|
|
@@ -868,13 +875,14 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
868
875
|
tempDir,
|
|
869
876
|
outputDir: outputBasePath,
|
|
870
877
|
target,
|
|
871
|
-
features: enabledFeatures
|
|
878
|
+
features: enabledFeatures,
|
|
879
|
+
logger: logger5
|
|
872
880
|
});
|
|
873
881
|
const results = convertedPaths.map((relativePath) => ({
|
|
874
882
|
relativePath,
|
|
875
883
|
status: "created"
|
|
876
884
|
}));
|
|
877
|
-
|
|
885
|
+
logger5.debug(`Converted ${converted} files from ${target} format to rulesync format`);
|
|
878
886
|
return {
|
|
879
887
|
source: `${parsed.owner}/${parsed.repo}`,
|
|
880
888
|
ref,
|
|
@@ -981,29 +989,30 @@ function formatFetchSummary(summary) {
|
|
|
981
989
|
}
|
|
982
990
|
|
|
983
991
|
// src/cli/commands/fetch.ts
|
|
984
|
-
async function fetchCommand(
|
|
992
|
+
async function fetchCommand(logger5, options) {
|
|
985
993
|
const { source, ...fetchOptions } = options;
|
|
986
|
-
|
|
994
|
+
logger5.debug(`Fetching files from ${source}...`);
|
|
987
995
|
try {
|
|
988
996
|
const summary = await fetchFiles({
|
|
989
997
|
source,
|
|
990
|
-
options: fetchOptions
|
|
998
|
+
options: fetchOptions,
|
|
999
|
+
logger: logger5
|
|
991
1000
|
});
|
|
992
|
-
if (
|
|
1001
|
+
if (logger5.jsonMode) {
|
|
993
1002
|
const createdFiles = summary.files.filter((f) => f.status === "created").map((f) => f.relativePath);
|
|
994
1003
|
const overwrittenFiles = summary.files.filter((f) => f.status === "overwritten").map((f) => f.relativePath);
|
|
995
1004
|
const skippedFiles = summary.files.filter((f) => f.status === "skipped").map((f) => f.relativePath);
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1005
|
+
logger5.captureData("source", source);
|
|
1006
|
+
logger5.captureData("path", fetchOptions.path);
|
|
1007
|
+
logger5.captureData("created", createdFiles);
|
|
1008
|
+
logger5.captureData("overwritten", overwrittenFiles);
|
|
1009
|
+
logger5.captureData("skipped", skippedFiles);
|
|
1010
|
+
logger5.captureData("totalFetched", summary.created + summary.overwritten + summary.skipped);
|
|
1002
1011
|
}
|
|
1003
1012
|
const output = formatFetchSummary(summary);
|
|
1004
|
-
|
|
1013
|
+
logger5.success(output);
|
|
1005
1014
|
if (summary.created + summary.overwritten === 0 && summary.skipped === 0) {
|
|
1006
|
-
|
|
1015
|
+
logger5.warn("No files were fetched.");
|
|
1007
1016
|
}
|
|
1008
1017
|
} catch (error) {
|
|
1009
1018
|
if (error instanceof GitHubClientError) {
|
|
@@ -1020,55 +1029,55 @@ function calculateTotalCount(result) {
|
|
|
1020
1029
|
}
|
|
1021
1030
|
|
|
1022
1031
|
// src/cli/commands/generate.ts
|
|
1023
|
-
function logFeatureResult(
|
|
1032
|
+
function logFeatureResult(logger5, params) {
|
|
1024
1033
|
const { count, paths, featureName, isPreview, modePrefix } = params;
|
|
1025
1034
|
if (count > 0) {
|
|
1026
1035
|
if (isPreview) {
|
|
1027
|
-
|
|
1036
|
+
logger5.info(`${modePrefix} Would write ${count} ${featureName}`);
|
|
1028
1037
|
} else {
|
|
1029
|
-
|
|
1038
|
+
logger5.success(`Written ${count} ${featureName}`);
|
|
1030
1039
|
}
|
|
1031
1040
|
for (const p of paths) {
|
|
1032
|
-
|
|
1041
|
+
logger5.info(` ${p}`);
|
|
1033
1042
|
}
|
|
1034
1043
|
}
|
|
1035
1044
|
}
|
|
1036
|
-
async function generateCommand(
|
|
1045
|
+
async function generateCommand(logger5, options) {
|
|
1037
1046
|
const config = await ConfigResolver.resolve(options);
|
|
1038
1047
|
const check = config.getCheck();
|
|
1039
1048
|
const isPreview = config.isPreviewMode();
|
|
1040
1049
|
const modePrefix = isPreview ? "[DRY RUN]" : "";
|
|
1041
|
-
|
|
1050
|
+
logger5.debug("Generating files...");
|
|
1042
1051
|
if (!await checkRulesyncDirExists({ baseDir: process.cwd() })) {
|
|
1043
1052
|
throw new CLIError(
|
|
1044
1053
|
".rulesync directory not found. Run 'rulesync init' first.",
|
|
1045
1054
|
ErrorCodes.RULESYNC_DIR_NOT_FOUND
|
|
1046
1055
|
);
|
|
1047
1056
|
}
|
|
1048
|
-
|
|
1057
|
+
logger5.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
|
|
1049
1058
|
const features = config.getFeatures();
|
|
1050
1059
|
if (features.includes("ignore")) {
|
|
1051
|
-
|
|
1060
|
+
logger5.debug("Generating ignore files...");
|
|
1052
1061
|
}
|
|
1053
1062
|
if (features.includes("mcp")) {
|
|
1054
|
-
|
|
1063
|
+
logger5.debug("Generating MCP files...");
|
|
1055
1064
|
}
|
|
1056
1065
|
if (features.includes("commands")) {
|
|
1057
|
-
|
|
1066
|
+
logger5.debug("Generating command files...");
|
|
1058
1067
|
}
|
|
1059
1068
|
if (features.includes("subagents")) {
|
|
1060
|
-
|
|
1069
|
+
logger5.debug("Generating subagent files...");
|
|
1061
1070
|
}
|
|
1062
1071
|
if (features.includes("skills")) {
|
|
1063
|
-
|
|
1072
|
+
logger5.debug("Generating skill files...");
|
|
1064
1073
|
}
|
|
1065
1074
|
if (features.includes("hooks")) {
|
|
1066
|
-
|
|
1075
|
+
logger5.debug("Generating hooks...");
|
|
1067
1076
|
}
|
|
1068
1077
|
if (features.includes("rules")) {
|
|
1069
|
-
|
|
1078
|
+
logger5.debug("Generating rule files...");
|
|
1070
1079
|
}
|
|
1071
|
-
const result = await generate({ config });
|
|
1080
|
+
const result = await generate({ config, logger: logger5 });
|
|
1072
1081
|
const totalGenerated = calculateTotalCount(result);
|
|
1073
1082
|
const featureResults = {
|
|
1074
1083
|
ignore: { count: result.ignoreCount, paths: result.ignorePaths },
|
|
@@ -1089,7 +1098,7 @@ async function generateCommand(logger2, options) {
|
|
|
1089
1098
|
hooks: (count) => `${count === 1 ? "hooks file" : "hooks files"}`
|
|
1090
1099
|
};
|
|
1091
1100
|
for (const [feature, data] of Object.entries(featureResults)) {
|
|
1092
|
-
logFeatureResult(
|
|
1101
|
+
logFeatureResult(logger5, {
|
|
1093
1102
|
count: data.count,
|
|
1094
1103
|
paths: data.paths,
|
|
1095
1104
|
featureName: featureLabels[feature]?.(data.count) ?? feature,
|
|
@@ -1097,15 +1106,15 @@ async function generateCommand(logger2, options) {
|
|
|
1097
1106
|
modePrefix
|
|
1098
1107
|
});
|
|
1099
1108
|
}
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1109
|
+
if (logger5.jsonMode) {
|
|
1110
|
+
logger5.captureData("features", featureResults);
|
|
1111
|
+
logger5.captureData("totalFiles", totalGenerated);
|
|
1112
|
+
logger5.captureData("hasDiff", result.hasDiff);
|
|
1113
|
+
logger5.captureData("skills", result.skills ?? []);
|
|
1105
1114
|
}
|
|
1106
1115
|
if (totalGenerated === 0) {
|
|
1107
1116
|
const enabledFeatures = features.join(", ");
|
|
1108
|
-
|
|
1117
|
+
logger5.info(`\u2713 All files are up to date (${enabledFeatures})`);
|
|
1109
1118
|
return;
|
|
1110
1119
|
}
|
|
1111
1120
|
const parts = [];
|
|
@@ -1117,9 +1126,9 @@ async function generateCommand(logger2, options) {
|
|
|
1117
1126
|
if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
|
|
1118
1127
|
if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
|
|
1119
1128
|
if (isPreview) {
|
|
1120
|
-
|
|
1129
|
+
logger5.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
|
|
1121
1130
|
} else {
|
|
1122
|
-
|
|
1131
|
+
logger5.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
|
|
1123
1132
|
}
|
|
1124
1133
|
if (check) {
|
|
1125
1134
|
if (result.hasDiff) {
|
|
@@ -1128,7 +1137,7 @@ async function generateCommand(logger2, options) {
|
|
|
1128
1137
|
ErrorCodes.GENERATION_FAILED
|
|
1129
1138
|
);
|
|
1130
1139
|
} else {
|
|
1131
|
-
|
|
1140
|
+
logger5.success("\u2713 All files are up to date.");
|
|
1132
1141
|
}
|
|
1133
1142
|
}
|
|
1134
1143
|
}
|
|
@@ -1202,6 +1211,8 @@ var GITIGNORE_ENTRY_REGISTRY = [
|
|
|
1202
1211
|
{ target: "copilot", feature: "skills", entry: "**/.github/skills/" },
|
|
1203
1212
|
{ target: "copilot", feature: "hooks", entry: "**/.github/hooks/" },
|
|
1204
1213
|
{ target: "copilot", feature: "mcp", entry: "**/.vscode/mcp.json" },
|
|
1214
|
+
// GitHub Copilot CLI
|
|
1215
|
+
{ target: "copilotcli", feature: "mcp", entry: "**/.copilot/mcp-config.json" },
|
|
1205
1216
|
// Junie
|
|
1206
1217
|
{ target: "junie", feature: "rules", entry: "**/.junie/guidelines.md" },
|
|
1207
1218
|
{ target: "junie", feature: "mcp", entry: "**/.junie/mcp.json" },
|
|
@@ -1264,46 +1275,47 @@ var isFeatureSelected = (feature, target, features) => {
|
|
|
1264
1275
|
if (targetFeatures.includes("*")) return true;
|
|
1265
1276
|
return targetFeatures.includes(feature);
|
|
1266
1277
|
};
|
|
1267
|
-
var warnInvalidTargets = (targets) => {
|
|
1278
|
+
var warnInvalidTargets = (targets, logger5) => {
|
|
1268
1279
|
const validTargets = new Set(ALL_TOOL_TARGETS_WITH_WILDCARD);
|
|
1269
1280
|
for (const target of targets) {
|
|
1270
1281
|
if (!validTargets.has(target)) {
|
|
1271
|
-
|
|
1282
|
+
logger5?.warn(
|
|
1272
1283
|
`Unknown target '${target}'. Valid targets: ${ALL_TOOL_TARGETS_WITH_WILDCARD.join(", ")}`
|
|
1273
1284
|
);
|
|
1274
1285
|
}
|
|
1275
1286
|
}
|
|
1276
1287
|
};
|
|
1277
|
-
var warnInvalidFeatures = (features) => {
|
|
1288
|
+
var warnInvalidFeatures = (features, logger5) => {
|
|
1278
1289
|
const validFeatures = new Set(ALL_FEATURES_WITH_WILDCARD);
|
|
1290
|
+
const warned = /* @__PURE__ */ new Set();
|
|
1291
|
+
const warnOnce = (feature) => {
|
|
1292
|
+
if (!validFeatures.has(feature) && !warned.has(feature)) {
|
|
1293
|
+
warned.add(feature);
|
|
1294
|
+
logger5?.warn(
|
|
1295
|
+
`Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1279
1299
|
if (Array.isArray(features)) {
|
|
1280
1300
|
for (const feature of features) {
|
|
1281
|
-
|
|
1282
|
-
logger.warn(
|
|
1283
|
-
`Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
|
|
1284
|
-
);
|
|
1285
|
-
}
|
|
1301
|
+
warnOnce(feature);
|
|
1286
1302
|
}
|
|
1287
1303
|
} else {
|
|
1288
1304
|
for (const targetFeatures of Object.values(features)) {
|
|
1289
1305
|
if (!targetFeatures) continue;
|
|
1290
1306
|
for (const feature of targetFeatures) {
|
|
1291
|
-
|
|
1292
|
-
logger.warn(
|
|
1293
|
-
`Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
|
|
1294
|
-
);
|
|
1295
|
-
}
|
|
1307
|
+
warnOnce(feature);
|
|
1296
1308
|
}
|
|
1297
1309
|
}
|
|
1298
1310
|
}
|
|
1299
1311
|
};
|
|
1300
1312
|
var filterGitignoreEntries = (params) => {
|
|
1301
|
-
const { targets, features } = params ?? {};
|
|
1313
|
+
const { targets, features, logger: logger5 } = params ?? {};
|
|
1302
1314
|
if (targets && targets.length > 0) {
|
|
1303
|
-
warnInvalidTargets(targets);
|
|
1315
|
+
warnInvalidTargets(targets, logger5);
|
|
1304
1316
|
}
|
|
1305
1317
|
if (features) {
|
|
1306
|
-
warnInvalidFeatures(features);
|
|
1318
|
+
warnInvalidFeatures(features, logger5);
|
|
1307
1319
|
}
|
|
1308
1320
|
const seen = /* @__PURE__ */ new Set();
|
|
1309
1321
|
const result = [];
|
|
@@ -1369,7 +1381,7 @@ var removeExistingRulesyncEntries = (content) => {
|
|
|
1369
1381
|
}
|
|
1370
1382
|
return result;
|
|
1371
1383
|
};
|
|
1372
|
-
var gitignoreCommand = async (
|
|
1384
|
+
var gitignoreCommand = async (logger5, options) => {
|
|
1373
1385
|
const gitignorePath = join2(process.cwd(), ".gitignore");
|
|
1374
1386
|
let gitignoreContent = "";
|
|
1375
1387
|
if (await fileExists(gitignorePath)) {
|
|
@@ -1378,7 +1390,8 @@ var gitignoreCommand = async (logger2, options) => {
|
|
|
1378
1390
|
const cleanedContent = removeExistingRulesyncEntries(gitignoreContent);
|
|
1379
1391
|
const filteredEntries = filterGitignoreEntries({
|
|
1380
1392
|
targets: options?.targets,
|
|
1381
|
-
features: options?.features
|
|
1393
|
+
features: options?.features,
|
|
1394
|
+
logger: logger5
|
|
1382
1395
|
});
|
|
1383
1396
|
const existingEntries = new Set(
|
|
1384
1397
|
gitignoreContent.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line))
|
|
@@ -1392,37 +1405,37 @@ ${rulesyncBlock}
|
|
|
1392
1405
|
` : `${rulesyncBlock}
|
|
1393
1406
|
`;
|
|
1394
1407
|
if (gitignoreContent === newContent) {
|
|
1395
|
-
if (
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1408
|
+
if (logger5.jsonMode) {
|
|
1409
|
+
logger5.captureData("entriesAdded", []);
|
|
1410
|
+
logger5.captureData("gitignorePath", gitignorePath);
|
|
1411
|
+
logger5.captureData("alreadyExisted", filteredEntries);
|
|
1399
1412
|
}
|
|
1400
|
-
|
|
1413
|
+
logger5.success(".gitignore is already up to date");
|
|
1401
1414
|
return;
|
|
1402
1415
|
}
|
|
1403
1416
|
await writeFileContent(gitignorePath, newContent);
|
|
1404
|
-
if (
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1417
|
+
if (logger5.jsonMode) {
|
|
1418
|
+
logger5.captureData("entriesAdded", entriesToAdd);
|
|
1419
|
+
logger5.captureData("gitignorePath", gitignorePath);
|
|
1420
|
+
logger5.captureData("alreadyExisted", alreadyExistedEntries);
|
|
1408
1421
|
}
|
|
1409
|
-
|
|
1422
|
+
logger5.success("Updated .gitignore with rulesync entries:");
|
|
1410
1423
|
for (const entry of filteredEntries) {
|
|
1411
|
-
|
|
1424
|
+
logger5.info(` ${entry}`);
|
|
1412
1425
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1426
|
+
logger5.info("");
|
|
1427
|
+
logger5.info(
|
|
1415
1428
|
"\u{1F4A1} If you're using Google Antigravity, note that rules, workflows, and skills won't load if they're gitignored."
|
|
1416
1429
|
);
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1430
|
+
logger5.info(" You can add the following to .git/info/exclude instead:");
|
|
1431
|
+
logger5.info(" **/.agent/rules/");
|
|
1432
|
+
logger5.info(" **/.agent/workflows/");
|
|
1433
|
+
logger5.info(" **/.agent/skills/");
|
|
1434
|
+
logger5.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
|
|
1422
1435
|
};
|
|
1423
1436
|
|
|
1424
1437
|
// src/cli/commands/import.ts
|
|
1425
|
-
async function importCommand(
|
|
1438
|
+
async function importCommand(logger5, options) {
|
|
1426
1439
|
if (!options.targets) {
|
|
1427
1440
|
throw new CLIError("No tools found in --targets", ErrorCodes.IMPORT_FAILED);
|
|
1428
1441
|
}
|
|
@@ -1431,17 +1444,17 @@ async function importCommand(logger2, options) {
|
|
|
1431
1444
|
}
|
|
1432
1445
|
const config = await ConfigResolver.resolve(options);
|
|
1433
1446
|
const tool = config.getTargets()[0];
|
|
1434
|
-
|
|
1435
|
-
const result = await importFromTool({ config, tool });
|
|
1447
|
+
logger5.debug(`Importing files from ${tool}...`);
|
|
1448
|
+
const result = await importFromTool({ config, tool, logger: logger5 });
|
|
1436
1449
|
const totalImported = calculateTotalCount(result);
|
|
1437
1450
|
if (totalImported === 0) {
|
|
1438
1451
|
const enabledFeatures = config.getFeatures().join(", ");
|
|
1439
|
-
|
|
1452
|
+
logger5.warn(`No files imported for enabled features: ${enabledFeatures}`);
|
|
1440
1453
|
return;
|
|
1441
1454
|
}
|
|
1442
|
-
if (
|
|
1443
|
-
|
|
1444
|
-
|
|
1455
|
+
if (logger5.jsonMode) {
|
|
1456
|
+
logger5.captureData("tool", tool);
|
|
1457
|
+
logger5.captureData("features", {
|
|
1445
1458
|
rules: { count: result.rulesCount },
|
|
1446
1459
|
ignore: { count: result.ignoreCount },
|
|
1447
1460
|
mcp: { count: result.mcpCount },
|
|
@@ -1450,7 +1463,7 @@ async function importCommand(logger2, options) {
|
|
|
1450
1463
|
skills: { count: result.skillsCount },
|
|
1451
1464
|
hooks: { count: result.hooksCount }
|
|
1452
1465
|
});
|
|
1453
|
-
|
|
1466
|
+
logger5.captureData("totalFiles", totalImported);
|
|
1454
1467
|
}
|
|
1455
1468
|
const parts = [];
|
|
1456
1469
|
if (result.rulesCount > 0) parts.push(`${result.rulesCount} rules`);
|
|
@@ -1460,7 +1473,7 @@ async function importCommand(logger2, options) {
|
|
|
1460
1473
|
if (result.subagentsCount > 0) parts.push(`${result.subagentsCount} subagents`);
|
|
1461
1474
|
if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
|
|
1462
1475
|
if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
|
|
1463
|
-
|
|
1476
|
+
logger5.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
|
|
1464
1477
|
}
|
|
1465
1478
|
|
|
1466
1479
|
// src/lib/init.ts
|
|
@@ -1689,8 +1702,8 @@ async function writeIfNotExists(path2, content) {
|
|
|
1689
1702
|
}
|
|
1690
1703
|
|
|
1691
1704
|
// src/cli/commands/init.ts
|
|
1692
|
-
async function initCommand(
|
|
1693
|
-
|
|
1705
|
+
async function initCommand(logger5) {
|
|
1706
|
+
logger5.debug("Initializing rulesync...");
|
|
1694
1707
|
await ensureDir(RULESYNC_RELATIVE_DIR_PATH);
|
|
1695
1708
|
const result = await init();
|
|
1696
1709
|
const createdFiles = [];
|
|
@@ -1698,29 +1711,29 @@ async function initCommand(logger2) {
|
|
|
1698
1711
|
for (const file of result.sampleFiles) {
|
|
1699
1712
|
if (file.created) {
|
|
1700
1713
|
createdFiles.push(file.path);
|
|
1701
|
-
|
|
1714
|
+
logger5.success(`Created ${file.path}`);
|
|
1702
1715
|
} else {
|
|
1703
1716
|
skippedFiles.push(file.path);
|
|
1704
|
-
|
|
1717
|
+
logger5.info(`Skipped ${file.path} (already exists)`);
|
|
1705
1718
|
}
|
|
1706
1719
|
}
|
|
1707
1720
|
if (result.configFile.created) {
|
|
1708
1721
|
createdFiles.push(result.configFile.path);
|
|
1709
|
-
|
|
1722
|
+
logger5.success(`Created ${result.configFile.path}`);
|
|
1710
1723
|
} else {
|
|
1711
1724
|
skippedFiles.push(result.configFile.path);
|
|
1712
|
-
|
|
1725
|
+
logger5.info(`Skipped ${result.configFile.path} (already exists)`);
|
|
1713
1726
|
}
|
|
1714
|
-
if (
|
|
1715
|
-
|
|
1716
|
-
|
|
1727
|
+
if (logger5.jsonMode) {
|
|
1728
|
+
logger5.captureData("created", createdFiles);
|
|
1729
|
+
logger5.captureData("skipped", skippedFiles);
|
|
1717
1730
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1731
|
+
logger5.success("rulesync initialized successfully!");
|
|
1732
|
+
logger5.info("Next steps:");
|
|
1733
|
+
logger5.info(
|
|
1721
1734
|
`1. Edit ${RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${RULESYNC_RELATIVE_DIR_PATH}/skills/*/${SKILL_FILE_NAME}, ${RULESYNC_MCP_RELATIVE_FILE_PATH}, ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} and ${RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`
|
|
1722
1735
|
);
|
|
1723
|
-
|
|
1736
|
+
logger5.info("2. Run 'rulesync generate' to create configuration files");
|
|
1724
1737
|
}
|
|
1725
1738
|
|
|
1726
1739
|
// src/lib/sources.ts
|
|
@@ -1741,7 +1754,7 @@ var GitClientError = class extends Error {
|
|
|
1741
1754
|
this.name = "GitClientError";
|
|
1742
1755
|
}
|
|
1743
1756
|
};
|
|
1744
|
-
function validateGitUrl(url) {
|
|
1757
|
+
function validateGitUrl(url, options) {
|
|
1745
1758
|
const ctrl = findControlCharacter(url);
|
|
1746
1759
|
if (ctrl) {
|
|
1747
1760
|
throw new GitClientError(
|
|
@@ -1754,7 +1767,7 @@ function validateGitUrl(url) {
|
|
|
1754
1767
|
);
|
|
1755
1768
|
}
|
|
1756
1769
|
if (INSECURE_URL_SCHEMES.test(url)) {
|
|
1757
|
-
logger
|
|
1770
|
+
options?.logger?.warn(
|
|
1758
1771
|
`URL "${url}" uses an unencrypted protocol. Consider using https:// or ssh:// instead.`
|
|
1759
1772
|
);
|
|
1760
1773
|
}
|
|
@@ -1814,8 +1827,8 @@ async function resolveRefToSha(url, ref) {
|
|
|
1814
1827
|
}
|
|
1815
1828
|
}
|
|
1816
1829
|
async function fetchSkillFiles(params) {
|
|
1817
|
-
const { url, ref, skillsPath } = params;
|
|
1818
|
-
validateGitUrl(url);
|
|
1830
|
+
const { url, ref, skillsPath, logger: logger5 } = params;
|
|
1831
|
+
validateGitUrl(url, { logger: logger5 });
|
|
1819
1832
|
validateRef(ref);
|
|
1820
1833
|
if (skillsPath.split(/[/\\]/).includes("..") || isAbsolute(skillsPath)) {
|
|
1821
1834
|
throw new GitClientError(
|
|
@@ -1853,7 +1866,7 @@ async function fetchSkillFiles(params) {
|
|
|
1853
1866
|
await execFileAsync("git", ["-C", tmpDir, "checkout"], { timeout: GIT_TIMEOUT_MS });
|
|
1854
1867
|
const skillsDir = join4(tmpDir, skillsPath);
|
|
1855
1868
|
if (!await directoryExists(skillsDir)) return [];
|
|
1856
|
-
return await walkDirectory(skillsDir, skillsDir);
|
|
1869
|
+
return await walkDirectory(skillsDir, skillsDir, 0, { totalFiles: 0, totalSize: 0 }, logger5);
|
|
1857
1870
|
} catch (error) {
|
|
1858
1871
|
if (error instanceof GitClientError) throw error;
|
|
1859
1872
|
throw new GitClientError(`Failed to fetch skill files from ${url}`, error);
|
|
@@ -1864,7 +1877,7 @@ async function fetchSkillFiles(params) {
|
|
|
1864
1877
|
var MAX_WALK_DEPTH = 20;
|
|
1865
1878
|
var MAX_TOTAL_FILES = 1e4;
|
|
1866
1879
|
var MAX_TOTAL_SIZE = 100 * 1024 * 1024;
|
|
1867
|
-
async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }) {
|
|
1880
|
+
async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }, logger5) {
|
|
1868
1881
|
if (depth > MAX_WALK_DEPTH) {
|
|
1869
1882
|
throw new GitClientError(
|
|
1870
1883
|
`Directory tree exceeds max depth of ${MAX_WALK_DEPTH}: "${dir}". Aborting to prevent resource exhaustion.`
|
|
@@ -1875,15 +1888,15 @@ async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, tot
|
|
|
1875
1888
|
if (name === ".git") continue;
|
|
1876
1889
|
const fullPath = join4(dir, name);
|
|
1877
1890
|
if (await isSymlink(fullPath)) {
|
|
1878
|
-
|
|
1891
|
+
logger5?.warn(`Skipping symlink "${fullPath}".`);
|
|
1879
1892
|
continue;
|
|
1880
1893
|
}
|
|
1881
1894
|
if (await directoryExists(fullPath)) {
|
|
1882
|
-
results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx));
|
|
1895
|
+
results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx, logger5));
|
|
1883
1896
|
} else {
|
|
1884
1897
|
const size = await getFileSize(fullPath);
|
|
1885
1898
|
if (size > MAX_FILE_SIZE) {
|
|
1886
|
-
|
|
1899
|
+
logger5?.warn(
|
|
1887
1900
|
`Skipping file "${fullPath}" (${(size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
|
|
1888
1901
|
);
|
|
1889
1902
|
continue;
|
|
@@ -1932,7 +1945,8 @@ var LegacyLockedSourceSchema = z4.object({
|
|
|
1932
1945
|
var LegacySourcesLockSchema = z4.object({
|
|
1933
1946
|
sources: z4.record(z4.string(), LegacyLockedSourceSchema)
|
|
1934
1947
|
});
|
|
1935
|
-
function migrateLegacyLock(
|
|
1948
|
+
function migrateLegacyLock(params) {
|
|
1949
|
+
const { legacy, logger: logger5 } = params;
|
|
1936
1950
|
const sources = {};
|
|
1937
1951
|
for (const [key, entry] of Object.entries(legacy.sources)) {
|
|
1938
1952
|
const skills = {};
|
|
@@ -1944,7 +1958,7 @@ function migrateLegacyLock(legacy) {
|
|
|
1944
1958
|
skills
|
|
1945
1959
|
};
|
|
1946
1960
|
}
|
|
1947
|
-
|
|
1961
|
+
logger5.info(
|
|
1948
1962
|
"Migrated legacy sources lockfile to version 1. Run 'rulesync install --update' to populate integrity hashes."
|
|
1949
1963
|
);
|
|
1950
1964
|
return { lockfileVersion: LOCKFILE_VERSION, sources };
|
|
@@ -1953,9 +1967,10 @@ function createEmptyLock() {
|
|
|
1953
1967
|
return { lockfileVersion: LOCKFILE_VERSION, sources: {} };
|
|
1954
1968
|
}
|
|
1955
1969
|
async function readLockFile(params) {
|
|
1970
|
+
const { logger: logger5 } = params;
|
|
1956
1971
|
const lockPath = join5(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
|
|
1957
1972
|
if (!await fileExists(lockPath)) {
|
|
1958
|
-
|
|
1973
|
+
logger5.debug("No sources lockfile found, starting fresh.");
|
|
1959
1974
|
return createEmptyLock();
|
|
1960
1975
|
}
|
|
1961
1976
|
try {
|
|
@@ -1967,24 +1982,25 @@ async function readLockFile(params) {
|
|
|
1967
1982
|
}
|
|
1968
1983
|
const legacyResult = LegacySourcesLockSchema.safeParse(data);
|
|
1969
1984
|
if (legacyResult.success) {
|
|
1970
|
-
return migrateLegacyLock(legacyResult.data);
|
|
1985
|
+
return migrateLegacyLock({ legacy: legacyResult.data, logger: logger5 });
|
|
1971
1986
|
}
|
|
1972
|
-
|
|
1987
|
+
logger5.warn(
|
|
1973
1988
|
`Invalid sources lockfile format (${RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH}). Starting fresh.`
|
|
1974
1989
|
);
|
|
1975
1990
|
return createEmptyLock();
|
|
1976
1991
|
} catch {
|
|
1977
|
-
|
|
1992
|
+
logger5.warn(
|
|
1978
1993
|
`Failed to read sources lockfile (${RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH}). Starting fresh.`
|
|
1979
1994
|
);
|
|
1980
1995
|
return createEmptyLock();
|
|
1981
1996
|
}
|
|
1982
1997
|
}
|
|
1983
1998
|
async function writeLockFile(params) {
|
|
1999
|
+
const { logger: logger5 } = params;
|
|
1984
2000
|
const lockPath = join5(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
|
|
1985
2001
|
const content = JSON.stringify(params.lock, null, 2) + "\n";
|
|
1986
2002
|
await writeFileContent(lockPath, content);
|
|
1987
|
-
|
|
2003
|
+
logger5.debug(`Wrote sources lockfile to ${lockPath}`);
|
|
1988
2004
|
}
|
|
1989
2005
|
function computeSkillIntegrity(files) {
|
|
1990
2006
|
const hash = createHash("sha256");
|
|
@@ -2056,15 +2072,15 @@ function getLockedSkillNames(entry) {
|
|
|
2056
2072
|
|
|
2057
2073
|
// src/lib/sources.ts
|
|
2058
2074
|
async function resolveAndFetchSources(params) {
|
|
2059
|
-
const { sources, baseDir, options = {} } = params;
|
|
2075
|
+
const { sources, baseDir, options = {}, logger: logger5 } = params;
|
|
2060
2076
|
if (sources.length === 0) {
|
|
2061
2077
|
return { fetchedSkillCount: 0, sourcesProcessed: 0 };
|
|
2062
2078
|
}
|
|
2063
2079
|
if (options.skipSources) {
|
|
2064
|
-
|
|
2080
|
+
logger5.info("Skipping source fetching.");
|
|
2065
2081
|
return { fetchedSkillCount: 0, sourcesProcessed: 0 };
|
|
2066
2082
|
}
|
|
2067
|
-
let lock = options.updateSources ? createEmptyLock() : await readLockFile({ baseDir });
|
|
2083
|
+
let lock = options.updateSources ? createEmptyLock() : await readLockFile({ baseDir, logger: logger5 });
|
|
2068
2084
|
if (options.frozen) {
|
|
2069
2085
|
const missingKeys = [];
|
|
2070
2086
|
for (const source of sources) {
|
|
@@ -2097,7 +2113,8 @@ async function resolveAndFetchSources(params) {
|
|
|
2097
2113
|
localSkillNames,
|
|
2098
2114
|
alreadyFetchedSkillNames: allFetchedSkillNames,
|
|
2099
2115
|
updateSources: options.updateSources ?? false,
|
|
2100
|
-
frozen: options.frozen ?? false
|
|
2116
|
+
frozen: options.frozen ?? false,
|
|
2117
|
+
logger: logger5
|
|
2101
2118
|
});
|
|
2102
2119
|
} else {
|
|
2103
2120
|
result = await fetchSource({
|
|
@@ -2107,7 +2124,8 @@ async function resolveAndFetchSources(params) {
|
|
|
2107
2124
|
lock,
|
|
2108
2125
|
localSkillNames,
|
|
2109
2126
|
alreadyFetchedSkillNames: allFetchedSkillNames,
|
|
2110
|
-
updateSources: options.updateSources ?? false
|
|
2127
|
+
updateSources: options.updateSources ?? false,
|
|
2128
|
+
logger: logger5
|
|
2111
2129
|
});
|
|
2112
2130
|
}
|
|
2113
2131
|
const { skillCount, fetchedSkillNames, updatedLock } = result;
|
|
@@ -2117,11 +2135,11 @@ async function resolveAndFetchSources(params) {
|
|
|
2117
2135
|
allFetchedSkillNames.add(name);
|
|
2118
2136
|
}
|
|
2119
2137
|
} catch (error) {
|
|
2120
|
-
|
|
2138
|
+
logger5.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
|
|
2121
2139
|
if (error instanceof GitHubClientError) {
|
|
2122
|
-
logGitHubAuthHints(error);
|
|
2140
|
+
logGitHubAuthHints({ error, logger: logger5 });
|
|
2123
2141
|
} else if (error instanceof GitClientError) {
|
|
2124
|
-
logGitClientHints(error);
|
|
2142
|
+
logGitClientHints({ error, logger: logger5 });
|
|
2125
2143
|
}
|
|
2126
2144
|
}
|
|
2127
2145
|
}
|
|
@@ -2131,22 +2149,23 @@ async function resolveAndFetchSources(params) {
|
|
|
2131
2149
|
if (sourceKeys.has(normalizeSourceKey(key))) {
|
|
2132
2150
|
prunedSources[key] = value;
|
|
2133
2151
|
} else {
|
|
2134
|
-
|
|
2152
|
+
logger5.debug(`Pruned stale lockfile entry: ${key}`);
|
|
2135
2153
|
}
|
|
2136
2154
|
}
|
|
2137
2155
|
lock = { lockfileVersion: lock.lockfileVersion, sources: prunedSources };
|
|
2138
2156
|
if (!options.frozen && JSON.stringify(lock) !== originalLockJson) {
|
|
2139
|
-
await writeLockFile({ baseDir, lock });
|
|
2157
|
+
await writeLockFile({ baseDir, lock, logger: logger5 });
|
|
2140
2158
|
} else {
|
|
2141
|
-
|
|
2159
|
+
logger5.debug("Lockfile unchanged, skipping write.");
|
|
2142
2160
|
}
|
|
2143
2161
|
return { fetchedSkillCount: totalSkillCount, sourcesProcessed: sources.length };
|
|
2144
2162
|
}
|
|
2145
|
-
function logGitClientHints(
|
|
2163
|
+
function logGitClientHints(params) {
|
|
2164
|
+
const { error, logger: logger5 } = params;
|
|
2146
2165
|
if (error.message.includes("not installed")) {
|
|
2147
|
-
|
|
2166
|
+
logger5.info("Hint: Install git and ensure it is available on your PATH.");
|
|
2148
2167
|
} else {
|
|
2149
|
-
|
|
2168
|
+
logger5.info("Hint: Check your git credentials (SSH keys, credential helper, or access token).");
|
|
2150
2169
|
}
|
|
2151
2170
|
}
|
|
2152
2171
|
async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
@@ -2158,12 +2177,13 @@ async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
|
2158
2177
|
}
|
|
2159
2178
|
return true;
|
|
2160
2179
|
}
|
|
2161
|
-
async function cleanPreviousCuratedSkills(
|
|
2180
|
+
async function cleanPreviousCuratedSkills(params) {
|
|
2181
|
+
const { curatedDir, lockedSkillNames, logger: logger5 } = params;
|
|
2162
2182
|
const resolvedCuratedDir = resolve(curatedDir);
|
|
2163
2183
|
for (const prevSkill of lockedSkillNames) {
|
|
2164
2184
|
const prevDir = join6(curatedDir, prevSkill);
|
|
2165
2185
|
if (!resolve(prevDir).startsWith(resolvedCuratedDir + sep)) {
|
|
2166
|
-
|
|
2186
|
+
logger5.warn(
|
|
2167
2187
|
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
2168
2188
|
);
|
|
2169
2189
|
continue;
|
|
@@ -2174,21 +2194,21 @@ async function cleanPreviousCuratedSkills(curatedDir, lockedSkillNames) {
|
|
|
2174
2194
|
}
|
|
2175
2195
|
}
|
|
2176
2196
|
function shouldSkipSkill(params) {
|
|
2177
|
-
const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames } = params;
|
|
2197
|
+
const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames, logger: logger5 } = params;
|
|
2178
2198
|
if (skillName.includes("..") || skillName.includes("/") || skillName.includes("\\")) {
|
|
2179
|
-
|
|
2199
|
+
logger5.warn(
|
|
2180
2200
|
`Skipping skill with invalid name "${skillName}" from ${sourceKey}: contains path traversal characters.`
|
|
2181
2201
|
);
|
|
2182
2202
|
return true;
|
|
2183
2203
|
}
|
|
2184
2204
|
if (localSkillNames.has(skillName)) {
|
|
2185
|
-
|
|
2205
|
+
logger5.debug(
|
|
2186
2206
|
`Skipping remote skill "${skillName}" from ${sourceKey}: local skill takes precedence.`
|
|
2187
2207
|
);
|
|
2188
2208
|
return true;
|
|
2189
2209
|
}
|
|
2190
2210
|
if (alreadyFetchedSkillNames.has(skillName)) {
|
|
2191
|
-
|
|
2211
|
+
logger5.warn(
|
|
2192
2212
|
`Skipping duplicate skill "${skillName}" from ${sourceKey}: already fetched from another source.`
|
|
2193
2213
|
);
|
|
2194
2214
|
return true;
|
|
@@ -2196,7 +2216,7 @@ function shouldSkipSkill(params) {
|
|
|
2196
2216
|
return false;
|
|
2197
2217
|
}
|
|
2198
2218
|
async function writeSkillAndComputeIntegrity(params) {
|
|
2199
|
-
const { skillName, files, curatedDir, locked, resolvedSha, sourceKey } = params;
|
|
2219
|
+
const { skillName, files, curatedDir, locked, resolvedSha, sourceKey, logger: logger5 } = params;
|
|
2200
2220
|
const written = [];
|
|
2201
2221
|
for (const file of files) {
|
|
2202
2222
|
checkPathTraversal({
|
|
@@ -2209,14 +2229,23 @@ async function writeSkillAndComputeIntegrity(params) {
|
|
|
2209
2229
|
const integrity = computeSkillIntegrity(written);
|
|
2210
2230
|
const lockedSkillEntry = locked?.skills[skillName];
|
|
2211
2231
|
if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
|
|
2212
|
-
|
|
2232
|
+
logger5.warn(
|
|
2213
2233
|
`Integrity mismatch for skill "${skillName}" from ${sourceKey}: expected "${lockedSkillEntry.integrity}", got "${integrity}". Content may have been tampered with.`
|
|
2214
2234
|
);
|
|
2215
2235
|
}
|
|
2216
2236
|
return { integrity };
|
|
2217
2237
|
}
|
|
2218
2238
|
function buildLockUpdate(params) {
|
|
2219
|
-
const {
|
|
2239
|
+
const {
|
|
2240
|
+
lock,
|
|
2241
|
+
sourceKey,
|
|
2242
|
+
fetchedSkills,
|
|
2243
|
+
locked,
|
|
2244
|
+
requestedRef,
|
|
2245
|
+
resolvedSha,
|
|
2246
|
+
remoteSkillNames,
|
|
2247
|
+
logger: logger5
|
|
2248
|
+
} = params;
|
|
2220
2249
|
const fetchedNames = Object.keys(fetchedSkills);
|
|
2221
2250
|
const remoteSet = new Set(remoteSkillNames);
|
|
2222
2251
|
const mergedSkills = { ...fetchedSkills };
|
|
@@ -2233,17 +2262,25 @@ function buildLockUpdate(params) {
|
|
|
2233
2262
|
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2234
2263
|
skills: mergedSkills
|
|
2235
2264
|
});
|
|
2236
|
-
|
|
2265
|
+
logger5.info(
|
|
2237
2266
|
`Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
|
|
2238
2267
|
);
|
|
2239
2268
|
return { updatedLock, fetchedNames };
|
|
2240
2269
|
}
|
|
2241
2270
|
async function fetchSource(params) {
|
|
2242
|
-
const {
|
|
2271
|
+
const {
|
|
2272
|
+
sourceEntry,
|
|
2273
|
+
client,
|
|
2274
|
+
baseDir,
|
|
2275
|
+
localSkillNames,
|
|
2276
|
+
alreadyFetchedSkillNames,
|
|
2277
|
+
updateSources,
|
|
2278
|
+
logger: logger5
|
|
2279
|
+
} = params;
|
|
2243
2280
|
const { lock } = params;
|
|
2244
2281
|
const parsed = parseSource(sourceEntry.source);
|
|
2245
2282
|
if (parsed.provider === "gitlab") {
|
|
2246
|
-
|
|
2283
|
+
logger5.warn(`GitLab sources are not yet supported. Skipping "${sourceEntry.source}".`);
|
|
2247
2284
|
return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
|
|
2248
2285
|
}
|
|
2249
2286
|
const sourceKey = sourceEntry.source;
|
|
@@ -2256,18 +2293,18 @@ async function fetchSource(params) {
|
|
|
2256
2293
|
ref = locked.resolvedRef;
|
|
2257
2294
|
resolvedSha = locked.resolvedRef;
|
|
2258
2295
|
requestedRef = locked.requestedRef;
|
|
2259
|
-
|
|
2296
|
+
logger5.debug(`Using locked ref for ${sourceKey}: ${resolvedSha}`);
|
|
2260
2297
|
} else {
|
|
2261
2298
|
requestedRef = parsed.ref ?? await client.getDefaultBranch(parsed.owner, parsed.repo);
|
|
2262
2299
|
resolvedSha = await client.resolveRefToSha(parsed.owner, parsed.repo, requestedRef);
|
|
2263
2300
|
ref = resolvedSha;
|
|
2264
|
-
|
|
2301
|
+
logger5.debug(`Resolved ${sourceKey} ref "${requestedRef}" to SHA: ${resolvedSha}`);
|
|
2265
2302
|
}
|
|
2266
2303
|
const curatedDir = join6(baseDir, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
|
|
2267
2304
|
if (locked && resolvedSha === locked.resolvedRef && !updateSources) {
|
|
2268
2305
|
const allExist = await checkLockedSkillsExist(curatedDir, lockedSkillNames);
|
|
2269
2306
|
if (allExist) {
|
|
2270
|
-
|
|
2307
|
+
logger5.debug(`SHA unchanged for ${sourceKey}, skipping re-fetch.`);
|
|
2271
2308
|
return {
|
|
2272
2309
|
skillCount: 0,
|
|
2273
2310
|
fetchedSkillNames: lockedSkillNames,
|
|
@@ -2284,7 +2321,7 @@ async function fetchSource(params) {
|
|
|
2284
2321
|
remoteSkillDirs = entries.filter((e) => e.type === "dir").map((e) => ({ name: e.name, path: e.path }));
|
|
2285
2322
|
} catch (error) {
|
|
2286
2323
|
if (error instanceof GitHubClientError && error.statusCode === 404) {
|
|
2287
|
-
|
|
2324
|
+
logger5.warn(`No skills/ directory found in ${sourceKey}. Skipping.`);
|
|
2288
2325
|
return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
|
|
2289
2326
|
}
|
|
2290
2327
|
throw error;
|
|
@@ -2293,14 +2330,15 @@ async function fetchSource(params) {
|
|
|
2293
2330
|
const semaphore = new Semaphore2(FETCH_CONCURRENCY_LIMIT);
|
|
2294
2331
|
const fetchedSkills = {};
|
|
2295
2332
|
if (locked) {
|
|
2296
|
-
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
2333
|
+
await cleanPreviousCuratedSkills({ curatedDir, lockedSkillNames, logger: logger5 });
|
|
2297
2334
|
}
|
|
2298
2335
|
for (const skillDir of filteredDirs) {
|
|
2299
2336
|
if (shouldSkipSkill({
|
|
2300
2337
|
skillName: skillDir.name,
|
|
2301
2338
|
sourceKey,
|
|
2302
2339
|
localSkillNames,
|
|
2303
|
-
alreadyFetchedSkillNames
|
|
2340
|
+
alreadyFetchedSkillNames,
|
|
2341
|
+
logger: logger5
|
|
2304
2342
|
})) {
|
|
2305
2343
|
continue;
|
|
2306
2344
|
}
|
|
@@ -2314,7 +2352,7 @@ async function fetchSource(params) {
|
|
|
2314
2352
|
});
|
|
2315
2353
|
const files = allFiles.filter((file) => {
|
|
2316
2354
|
if (file.size > MAX_FILE_SIZE) {
|
|
2317
|
-
|
|
2355
|
+
logger5.warn(
|
|
2318
2356
|
`Skipping file "${file.path}" (${(file.size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
|
|
2319
2357
|
);
|
|
2320
2358
|
return false;
|
|
@@ -2336,9 +2374,10 @@ async function fetchSource(params) {
|
|
|
2336
2374
|
curatedDir,
|
|
2337
2375
|
locked,
|
|
2338
2376
|
resolvedSha,
|
|
2339
|
-
sourceKey
|
|
2377
|
+
sourceKey,
|
|
2378
|
+
logger: logger5
|
|
2340
2379
|
});
|
|
2341
|
-
|
|
2380
|
+
logger5.debug(`Fetched skill "${skillDir.name}" from ${sourceKey}`);
|
|
2342
2381
|
}
|
|
2343
2382
|
const result = buildLockUpdate({
|
|
2344
2383
|
lock,
|
|
@@ -2347,7 +2386,8 @@ async function fetchSource(params) {
|
|
|
2347
2386
|
locked,
|
|
2348
2387
|
requestedRef,
|
|
2349
2388
|
resolvedSha,
|
|
2350
|
-
remoteSkillNames: filteredDirs.map((d) => d.name)
|
|
2389
|
+
remoteSkillNames: filteredDirs.map((d) => d.name),
|
|
2390
|
+
logger: logger5
|
|
2351
2391
|
});
|
|
2352
2392
|
return {
|
|
2353
2393
|
skillCount: result.fetchedNames.length,
|
|
@@ -2356,7 +2396,15 @@ async function fetchSource(params) {
|
|
|
2356
2396
|
};
|
|
2357
2397
|
}
|
|
2358
2398
|
async function fetchSourceViaGit(params) {
|
|
2359
|
-
const {
|
|
2399
|
+
const {
|
|
2400
|
+
sourceEntry,
|
|
2401
|
+
baseDir,
|
|
2402
|
+
localSkillNames,
|
|
2403
|
+
alreadyFetchedSkillNames,
|
|
2404
|
+
updateSources,
|
|
2405
|
+
frozen,
|
|
2406
|
+
logger: logger5
|
|
2407
|
+
} = params;
|
|
2360
2408
|
const { lock } = params;
|
|
2361
2409
|
const url = sourceEntry.source;
|
|
2362
2410
|
const locked = getLockedSource(lock, url);
|
|
@@ -2413,11 +2461,17 @@ async function fetchSourceViaGit(params) {
|
|
|
2413
2461
|
const allNames = [...skillFileMap.keys()];
|
|
2414
2462
|
const filteredNames = isWildcard ? allNames : allNames.filter((n) => skillFilter.includes(n));
|
|
2415
2463
|
if (locked) {
|
|
2416
|
-
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
2464
|
+
await cleanPreviousCuratedSkills({ curatedDir, lockedSkillNames, logger: logger5 });
|
|
2417
2465
|
}
|
|
2418
2466
|
const fetchedSkills = {};
|
|
2419
2467
|
for (const skillName of filteredNames) {
|
|
2420
|
-
if (shouldSkipSkill({
|
|
2468
|
+
if (shouldSkipSkill({
|
|
2469
|
+
skillName,
|
|
2470
|
+
sourceKey: url,
|
|
2471
|
+
localSkillNames,
|
|
2472
|
+
alreadyFetchedSkillNames,
|
|
2473
|
+
logger: logger5
|
|
2474
|
+
})) {
|
|
2421
2475
|
continue;
|
|
2422
2476
|
}
|
|
2423
2477
|
fetchedSkills[skillName] = await writeSkillAndComputeIntegrity({
|
|
@@ -2426,7 +2480,8 @@ async function fetchSourceViaGit(params) {
|
|
|
2426
2480
|
curatedDir,
|
|
2427
2481
|
locked,
|
|
2428
2482
|
resolvedSha,
|
|
2429
|
-
sourceKey: url
|
|
2483
|
+
sourceKey: url,
|
|
2484
|
+
logger: logger5
|
|
2430
2485
|
});
|
|
2431
2486
|
}
|
|
2432
2487
|
const result = buildLockUpdate({
|
|
@@ -2436,7 +2491,8 @@ async function fetchSourceViaGit(params) {
|
|
|
2436
2491
|
locked,
|
|
2437
2492
|
requestedRef,
|
|
2438
2493
|
resolvedSha,
|
|
2439
|
-
remoteSkillNames: filteredNames
|
|
2494
|
+
remoteSkillNames: filteredNames,
|
|
2495
|
+
logger: logger5
|
|
2440
2496
|
});
|
|
2441
2497
|
return {
|
|
2442
2498
|
skillCount: result.fetchedNames.length,
|
|
@@ -2446,7 +2502,7 @@ async function fetchSourceViaGit(params) {
|
|
|
2446
2502
|
}
|
|
2447
2503
|
|
|
2448
2504
|
// src/cli/commands/install.ts
|
|
2449
|
-
async function installCommand(
|
|
2505
|
+
async function installCommand(logger5, options) {
|
|
2450
2506
|
const config = await ConfigResolver.resolve({
|
|
2451
2507
|
configPath: options.configPath,
|
|
2452
2508
|
verbose: options.verbose,
|
|
@@ -2454,10 +2510,10 @@ async function installCommand(logger2, options) {
|
|
|
2454
2510
|
});
|
|
2455
2511
|
const sources = config.getSources();
|
|
2456
2512
|
if (sources.length === 0) {
|
|
2457
|
-
|
|
2513
|
+
logger5.warn("No sources defined in configuration. Nothing to install.");
|
|
2458
2514
|
return;
|
|
2459
2515
|
}
|
|
2460
|
-
|
|
2516
|
+
logger5.debug(`Installing skills from ${sources.length} source(s)...`);
|
|
2461
2517
|
const result = await resolveAndFetchSources({
|
|
2462
2518
|
sources,
|
|
2463
2519
|
baseDir: process.cwd(),
|
|
@@ -2465,18 +2521,19 @@ async function installCommand(logger2, options) {
|
|
|
2465
2521
|
updateSources: options.update,
|
|
2466
2522
|
frozen: options.frozen,
|
|
2467
2523
|
token: options.token
|
|
2468
|
-
}
|
|
2524
|
+
},
|
|
2525
|
+
logger: logger5
|
|
2469
2526
|
});
|
|
2470
|
-
if (
|
|
2471
|
-
|
|
2472
|
-
|
|
2527
|
+
if (logger5.jsonMode) {
|
|
2528
|
+
logger5.captureData("sourcesProcessed", result.sourcesProcessed);
|
|
2529
|
+
logger5.captureData("skillsFetched", result.fetchedSkillCount);
|
|
2473
2530
|
}
|
|
2474
2531
|
if (result.fetchedSkillCount > 0) {
|
|
2475
|
-
|
|
2532
|
+
logger5.success(
|
|
2476
2533
|
`Installed ${result.fetchedSkillCount} skill(s) from ${result.sourcesProcessed} source(s).`
|
|
2477
2534
|
);
|
|
2478
2535
|
} else {
|
|
2479
|
-
|
|
2536
|
+
logger5.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
|
|
2480
2537
|
}
|
|
2481
2538
|
}
|
|
2482
2539
|
|
|
@@ -2489,6 +2546,7 @@ import { z as z13 } from "zod/mini";
|
|
|
2489
2546
|
// src/mcp/commands.ts
|
|
2490
2547
|
import { basename, join as join7 } from "path";
|
|
2491
2548
|
import { z as z5 } from "zod/mini";
|
|
2549
|
+
var logger = new ConsoleLogger({ verbose: false, silent: true });
|
|
2492
2550
|
var maxCommandSizeBytes = 1024 * 1024;
|
|
2493
2551
|
var maxCommandsCount = 1e3;
|
|
2494
2552
|
async function listCommands() {
|
|
@@ -2707,7 +2765,8 @@ async function executeGenerate(options = {}) {
|
|
|
2707
2765
|
verbose: false,
|
|
2708
2766
|
silent: true
|
|
2709
2767
|
});
|
|
2710
|
-
const
|
|
2768
|
+
const logger5 = new ConsoleLogger({ verbose: false, silent: true });
|
|
2769
|
+
const generateResult = await generate({ config, logger: logger5 });
|
|
2711
2770
|
return buildSuccessResponse({ generateResult, config });
|
|
2712
2771
|
} catch (error) {
|
|
2713
2772
|
return {
|
|
@@ -2885,7 +2944,8 @@ async function executeImport(options) {
|
|
|
2885
2944
|
silent: true
|
|
2886
2945
|
});
|
|
2887
2946
|
const tool = config.getTargets()[0];
|
|
2888
|
-
const
|
|
2947
|
+
const logger5 = new ConsoleLogger({ verbose: false, silent: true });
|
|
2948
|
+
const importResult = await importFromTool({ config, tool, logger: logger5 });
|
|
2889
2949
|
return buildSuccessResponse2({ importResult, config, tool });
|
|
2890
2950
|
} catch (error) {
|
|
2891
2951
|
return {
|
|
@@ -3070,6 +3130,7 @@ var mcpTools = {
|
|
|
3070
3130
|
// src/mcp/rules.ts
|
|
3071
3131
|
import { basename as basename2, join as join10 } from "path";
|
|
3072
3132
|
import { z as z10 } from "zod/mini";
|
|
3133
|
+
var logger2 = new ConsoleLogger({ verbose: false, silent: true });
|
|
3073
3134
|
var maxRuleSizeBytes = 1024 * 1024;
|
|
3074
3135
|
var maxRulesCount = 1e3;
|
|
3075
3136
|
async function listRules() {
|
|
@@ -3090,14 +3151,14 @@ async function listRules() {
|
|
|
3090
3151
|
frontmatter
|
|
3091
3152
|
};
|
|
3092
3153
|
} catch (error) {
|
|
3093
|
-
|
|
3154
|
+
logger2.error(`Failed to read rule file ${file}: ${formatError(error)}`);
|
|
3094
3155
|
return null;
|
|
3095
3156
|
}
|
|
3096
3157
|
})
|
|
3097
3158
|
);
|
|
3098
3159
|
return rules.filter((rule) => rule !== null);
|
|
3099
3160
|
} catch (error) {
|
|
3100
|
-
|
|
3161
|
+
logger2.error(
|
|
3101
3162
|
`Failed to read rules directory (${RULESYNC_RULES_RELATIVE_DIR_PATH}): ${formatError(error)}`
|
|
3102
3163
|
);
|
|
3103
3164
|
return [];
|
|
@@ -3252,6 +3313,7 @@ var ruleTools = {
|
|
|
3252
3313
|
// src/mcp/skills.ts
|
|
3253
3314
|
import { basename as basename3, dirname, join as join11 } from "path";
|
|
3254
3315
|
import { z as z11 } from "zod/mini";
|
|
3316
|
+
var logger3 = new ConsoleLogger({ verbose: false, silent: true });
|
|
3255
3317
|
var maxSkillSizeBytes = 1024 * 1024;
|
|
3256
3318
|
var maxSkillsCount = 1e3;
|
|
3257
3319
|
function aiDirFileToMcpSkillFile(file) {
|
|
@@ -3291,14 +3353,14 @@ async function listSkills() {
|
|
|
3291
3353
|
frontmatter
|
|
3292
3354
|
};
|
|
3293
3355
|
} catch (error) {
|
|
3294
|
-
|
|
3356
|
+
logger3.error(`Failed to read skill directory ${dirName}: ${formatError(error)}`);
|
|
3295
3357
|
return null;
|
|
3296
3358
|
}
|
|
3297
3359
|
})
|
|
3298
3360
|
);
|
|
3299
3361
|
return skills.filter((skill) => skill !== null);
|
|
3300
3362
|
} catch (error) {
|
|
3301
|
-
|
|
3363
|
+
logger3.error(
|
|
3302
3364
|
`Failed to read skills directory (${RULESYNC_SKILLS_RELATIVE_DIR_PATH}): ${formatError(error)}`
|
|
3303
3365
|
);
|
|
3304
3366
|
return [];
|
|
@@ -3490,6 +3552,7 @@ var skillTools = {
|
|
|
3490
3552
|
// src/mcp/subagents.ts
|
|
3491
3553
|
import { basename as basename4, join as join12 } from "path";
|
|
3492
3554
|
import { z as z12 } from "zod/mini";
|
|
3555
|
+
var logger4 = new ConsoleLogger({ verbose: false, silent: true });
|
|
3493
3556
|
var maxSubagentSizeBytes = 1024 * 1024;
|
|
3494
3557
|
var maxSubagentsCount = 1e3;
|
|
3495
3558
|
async function listSubagents() {
|
|
@@ -3510,7 +3573,7 @@ async function listSubagents() {
|
|
|
3510
3573
|
frontmatter
|
|
3511
3574
|
};
|
|
3512
3575
|
} catch (error) {
|
|
3513
|
-
|
|
3576
|
+
logger4.error(`Failed to read subagent file ${file}: ${formatError(error)}`);
|
|
3514
3577
|
return null;
|
|
3515
3578
|
}
|
|
3516
3579
|
})
|
|
@@ -3519,7 +3582,7 @@ async function listSubagents() {
|
|
|
3519
3582
|
(subagent) => subagent !== null
|
|
3520
3583
|
);
|
|
3521
3584
|
} catch (error) {
|
|
3522
|
-
|
|
3585
|
+
logger4.error(
|
|
3523
3586
|
`Failed to read subagents directory (${RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH}): ${formatError(error)}`
|
|
3524
3587
|
);
|
|
3525
3588
|
return [];
|
|
@@ -3891,7 +3954,7 @@ var rulesyncTool = {
|
|
|
3891
3954
|
};
|
|
3892
3955
|
|
|
3893
3956
|
// src/cli/commands/mcp.ts
|
|
3894
|
-
async function mcpCommand(
|
|
3957
|
+
async function mcpCommand(logger5, { version }) {
|
|
3895
3958
|
const server = new FastMCP({
|
|
3896
3959
|
name: "Rulesync MCP Server",
|
|
3897
3960
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
@@ -3899,7 +3962,7 @@ async function mcpCommand(logger2, { version }) {
|
|
|
3899
3962
|
instructions: "This server handles Rulesync files including rules, commands, MCP, ignore files, subagents and skills for any AI agents. It should be used when you need those files."
|
|
3900
3963
|
});
|
|
3901
3964
|
server.addTool(rulesyncTool);
|
|
3902
|
-
|
|
3965
|
+
logger5.info("Rulesync MCP server started via stdio");
|
|
3903
3966
|
void server.start({
|
|
3904
3967
|
transportType: "stdio"
|
|
3905
3968
|
});
|
|
@@ -4215,43 +4278,43 @@ To upgrade, run:
|
|
|
4215
4278
|
}
|
|
4216
4279
|
|
|
4217
4280
|
// src/cli/commands/update.ts
|
|
4218
|
-
async function updateCommand(
|
|
4281
|
+
async function updateCommand(logger5, currentVersion, options) {
|
|
4219
4282
|
const { check = false, force = false, token } = options;
|
|
4220
4283
|
try {
|
|
4221
4284
|
const environment = detectExecutionEnvironment();
|
|
4222
|
-
|
|
4285
|
+
logger5.debug(`Detected environment: ${environment}`);
|
|
4223
4286
|
if (environment === "npm") {
|
|
4224
|
-
|
|
4287
|
+
logger5.info(getNpmUpgradeInstructions());
|
|
4225
4288
|
return;
|
|
4226
4289
|
}
|
|
4227
4290
|
if (environment === "homebrew") {
|
|
4228
|
-
|
|
4291
|
+
logger5.info(getHomebrewUpgradeInstructions());
|
|
4229
4292
|
return;
|
|
4230
4293
|
}
|
|
4231
4294
|
if (check) {
|
|
4232
|
-
|
|
4295
|
+
logger5.info("Checking for updates...");
|
|
4233
4296
|
const updateCheck = await checkForUpdate(currentVersion, token);
|
|
4234
|
-
if (
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4297
|
+
if (logger5.jsonMode) {
|
|
4298
|
+
logger5.captureData("currentVersion", updateCheck.currentVersion);
|
|
4299
|
+
logger5.captureData("latestVersion", updateCheck.latestVersion);
|
|
4300
|
+
logger5.captureData("updateAvailable", updateCheck.hasUpdate);
|
|
4301
|
+
logger5.captureData(
|
|
4239
4302
|
"message",
|
|
4240
4303
|
updateCheck.hasUpdate ? `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}` : `Already at the latest version (${updateCheck.currentVersion})`
|
|
4241
4304
|
);
|
|
4242
4305
|
}
|
|
4243
4306
|
if (updateCheck.hasUpdate) {
|
|
4244
|
-
|
|
4307
|
+
logger5.success(
|
|
4245
4308
|
`Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}`
|
|
4246
4309
|
);
|
|
4247
4310
|
} else {
|
|
4248
|
-
|
|
4311
|
+
logger5.info(`Already at the latest version (${updateCheck.currentVersion})`);
|
|
4249
4312
|
}
|
|
4250
4313
|
return;
|
|
4251
4314
|
}
|
|
4252
|
-
|
|
4315
|
+
logger5.info("Checking for updates...");
|
|
4253
4316
|
const message = await performBinaryUpdate(currentVersion, { force, token });
|
|
4254
|
-
|
|
4317
|
+
logger5.success(message);
|
|
4255
4318
|
} catch (error) {
|
|
4256
4319
|
if (error instanceof GitHubClientError) {
|
|
4257
4320
|
const authHint = error.statusCode === 401 || error.statusCode === 403 ? " Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable, or use `GITHUB_TOKEN=$(gh auth token) rulesync update ...`" : "";
|
|
@@ -4269,54 +4332,68 @@ async function updateCommand(logger2, currentVersion, options) {
|
|
|
4269
4332
|
}
|
|
4270
4333
|
}
|
|
4271
4334
|
|
|
4272
|
-
// src/cli/
|
|
4273
|
-
|
|
4274
|
-
|
|
4335
|
+
// src/cli/wrap-command.ts
|
|
4336
|
+
function createLogger({
|
|
4337
|
+
name,
|
|
4338
|
+
globalOpts,
|
|
4339
|
+
getVersion: getVersion2
|
|
4340
|
+
}) {
|
|
4341
|
+
return globalOpts.json ? new JsonLogger({ command: name, version: getVersion2() }) : new ConsoleLogger();
|
|
4342
|
+
}
|
|
4343
|
+
function wrapCommand({
|
|
4344
|
+
name,
|
|
4345
|
+
errorCode,
|
|
4346
|
+
handler,
|
|
4347
|
+
getVersion: getVersion2,
|
|
4348
|
+
loggerFactory = createLogger
|
|
4349
|
+
}) {
|
|
4275
4350
|
return async (...args) => {
|
|
4276
4351
|
const command = args[args.length - 1];
|
|
4277
4352
|
const options = args[args.length - 2];
|
|
4278
4353
|
const positionalArgs = args.slice(0, -2);
|
|
4279
4354
|
const globalOpts = command.parent?.opts() ?? {};
|
|
4280
|
-
const
|
|
4281
|
-
|
|
4355
|
+
const logger5 = loggerFactory({ name, globalOpts, getVersion: getVersion2 });
|
|
4356
|
+
logger5.configure({
|
|
4282
4357
|
verbose: Boolean(globalOpts.verbose) || Boolean(options.verbose),
|
|
4283
4358
|
silent: Boolean(globalOpts.silent) || Boolean(options.silent)
|
|
4284
4359
|
});
|
|
4285
4360
|
try {
|
|
4286
|
-
await handler(
|
|
4287
|
-
|
|
4361
|
+
await handler(logger5, options, globalOpts, positionalArgs);
|
|
4362
|
+
logger5.outputJson(true);
|
|
4288
4363
|
} catch (error) {
|
|
4289
4364
|
const code = error instanceof CLIError ? error.code : errorCode;
|
|
4290
4365
|
const errorArg = error instanceof Error ? error : formatError(error);
|
|
4291
|
-
|
|
4366
|
+
logger5.error(errorArg, code);
|
|
4292
4367
|
process.exit(error instanceof CLIError ? error.exitCode : 1);
|
|
4293
4368
|
}
|
|
4294
4369
|
};
|
|
4295
4370
|
}
|
|
4371
|
+
|
|
4372
|
+
// src/cli/index.ts
|
|
4373
|
+
var getVersion = () => "7.21.0";
|
|
4374
|
+
function wrapCommand2(name, errorCode, handler) {
|
|
4375
|
+
return wrapCommand({ name, errorCode, handler, getVersion });
|
|
4376
|
+
}
|
|
4296
4377
|
var main = async () => {
|
|
4297
4378
|
const program = new Command();
|
|
4298
4379
|
const version = getVersion();
|
|
4299
4380
|
program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version").option("-j, --json", "Output results as JSON");
|
|
4300
4381
|
program.command("init").description("Initialize rulesync in current directory").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4301
|
-
|
|
4302
|
-
await initCommand(
|
|
4382
|
+
wrapCommand2("init", "INIT_FAILED", async (logger5) => {
|
|
4383
|
+
await initCommand(logger5);
|
|
4303
4384
|
})
|
|
4304
4385
|
);
|
|
4305
4386
|
program.command("gitignore").description("Add generated files to .gitignore").option(
|
|
4306
4387
|
"-t, --targets <tools>",
|
|
4307
4388
|
"Comma-separated list of tools to include (e.g., 'claudecode,copilot' or '*' for all)",
|
|
4308
|
-
|
|
4309
|
-
return value.split(",").map((t) => t.trim()).filter(Boolean);
|
|
4310
|
-
}
|
|
4389
|
+
parseCommaSeparatedList
|
|
4311
4390
|
).option(
|
|
4312
4391
|
"-f, --features <features>",
|
|
4313
4392
|
`Comma-separated list of features to include (${ALL_FEATURES.join(",")}) or '*' for all`,
|
|
4314
|
-
|
|
4315
|
-
return value.split(",").map((f) => f.trim()).filter(Boolean);
|
|
4316
|
-
}
|
|
4393
|
+
parseCommaSeparatedList
|
|
4317
4394
|
).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4318
|
-
|
|
4319
|
-
await gitignoreCommand(
|
|
4395
|
+
wrapCommand2("gitignore", "GITIGNORE_FAILED", async (logger5, options) => {
|
|
4396
|
+
await gitignoreCommand(logger5, {
|
|
4320
4397
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
4321
4398
|
targets: options.targets,
|
|
4322
4399
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
@@ -4330,40 +4407,40 @@ var main = async () => {
|
|
|
4330
4407
|
).option(
|
|
4331
4408
|
"-f, --features <features>",
|
|
4332
4409
|
`Comma-separated list of features to fetch (${ALL_FEATURES.join(",")}) or '*' for all`,
|
|
4333
|
-
|
|
4410
|
+
parseCommaSeparatedList
|
|
4334
4411
|
).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(
|
|
4335
4412
|
"-c, --conflict <strategy>",
|
|
4336
4413
|
"Conflict resolution strategy: skip, overwrite (default: overwrite)"
|
|
4337
4414
|
).option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4338
|
-
|
|
4415
|
+
wrapCommand2("fetch", "FETCH_FAILED", async (logger5, options, _globalOpts, positionalArgs) => {
|
|
4339
4416
|
const source = positionalArgs[0];
|
|
4340
|
-
await fetchCommand(
|
|
4417
|
+
await fetchCommand(logger5, { ...options, source });
|
|
4341
4418
|
})
|
|
4342
4419
|
);
|
|
4343
4420
|
program.command("import").description("Import configurations from AI tools to rulesync format").option(
|
|
4344
4421
|
"-t, --targets <tool>",
|
|
4345
4422
|
"Tool to import from (e.g., 'copilot', 'cursor', 'cline')",
|
|
4346
|
-
|
|
4423
|
+
parseCommaSeparatedList
|
|
4347
4424
|
).option(
|
|
4348
4425
|
"-f, --features <features>",
|
|
4349
4426
|
`Comma-separated list of features to import (${ALL_FEATURES.join(",")}) or '*' for all`,
|
|
4350
|
-
|
|
4427
|
+
parseCommaSeparatedList
|
|
4351
4428
|
).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(
|
|
4352
|
-
|
|
4353
|
-
await importCommand(
|
|
4429
|
+
wrapCommand2("import", "IMPORT_FAILED", async (logger5, options) => {
|
|
4430
|
+
await importCommand(logger5, options);
|
|
4354
4431
|
})
|
|
4355
4432
|
);
|
|
4356
4433
|
program.command("mcp").description("Start MCP server for rulesync").action(
|
|
4357
|
-
|
|
4358
|
-
await mcpCommand(
|
|
4434
|
+
wrapCommand2("mcp", "MCP_FAILED", async (logger5, _options) => {
|
|
4435
|
+
await mcpCommand(logger5, { version });
|
|
4359
4436
|
})
|
|
4360
4437
|
);
|
|
4361
4438
|
program.command("install").description("Install skills from declarative sources in rulesync.jsonc").option("--update", "Force re-resolve all source refs, ignoring lockfile").option(
|
|
4362
4439
|
"--frozen",
|
|
4363
4440
|
"Fail if lockfile is missing or out of sync (for CI); fetches missing skills using locked refs"
|
|
4364
4441
|
).option("--token <token>", "GitHub token for private repos").option("-c, --config <path>", "Path to configuration file").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4365
|
-
|
|
4366
|
-
await installCommand(
|
|
4442
|
+
wrapCommand2("install", "INSTALL_FAILED", async (logger5, options) => {
|
|
4443
|
+
await installCommand(logger5, {
|
|
4367
4444
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
4368
4445
|
update: options.update,
|
|
4369
4446
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
@@ -4382,15 +4459,15 @@ var main = async () => {
|
|
|
4382
4459
|
program.command("generate").description("Generate configuration files for AI tools").option(
|
|
4383
4460
|
"-t, --targets <tools>",
|
|
4384
4461
|
"Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)",
|
|
4385
|
-
|
|
4462
|
+
parseCommaSeparatedList
|
|
4386
4463
|
).option(
|
|
4387
4464
|
"-f, --features <features>",
|
|
4388
4465
|
`Comma-separated list of features to generate (${ALL_FEATURES.join(",")}) or '*' for all`,
|
|
4389
|
-
|
|
4466
|
+
parseCommaSeparatedList
|
|
4390
4467
|
).option("--delete", "Delete all existing files in output directories before generating").option(
|
|
4391
4468
|
"-b, --base-dir <paths>",
|
|
4392
4469
|
"Base directories to generate files (comma-separated for multiple paths)",
|
|
4393
|
-
|
|
4470
|
+
parseCommaSeparatedList
|
|
4394
4471
|
).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(
|
|
4395
4472
|
"--simulate-commands",
|
|
4396
4473
|
"Generate simulated commands. This feature is only available for copilot, cursor and codexcli."
|
|
@@ -4401,13 +4478,13 @@ var main = async () => {
|
|
|
4401
4478
|
"--simulate-skills",
|
|
4402
4479
|
"Generate simulated skills. This feature is only available for copilot, cursor and codexcli."
|
|
4403
4480
|
).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(
|
|
4404
|
-
|
|
4405
|
-
await generateCommand(
|
|
4481
|
+
wrapCommand2("generate", "GENERATION_FAILED", async (logger5, options) => {
|
|
4482
|
+
await generateCommand(logger5, options);
|
|
4406
4483
|
})
|
|
4407
4484
|
);
|
|
4408
4485
|
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(
|
|
4409
|
-
|
|
4410
|
-
await updateCommand(
|
|
4486
|
+
wrapCommand2("update", "UPDATE_FAILED", async (logger5, options) => {
|
|
4487
|
+
await updateCommand(logger5, version, options);
|
|
4411
4488
|
})
|
|
4412
4489
|
);
|
|
4413
4490
|
program.parse();
|